﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Engine.Sgml.Merging;
using System.Diagnostics;
using Engine.Sgml.Validation.Rules;

namespace Engine.Sgml.Allocation
{
    internal class TreeNodeNavigator
    {
        private ValidationRuleCollection rules;
        private TreeNodeIterator treeNodeIterator;
        private InconsistentBlock block;
        private bool allocateBefore;
        private SElement current;

        public TreeNodeNavigator(ValidationRuleCollection validationRules)
        {
            Guard.ArgumentNotNull(validationRules, "validationRules");
            this.treeNodeIterator = new TreeNodeIterator();
            this.rules = validationRules;
        }

        public bool MoveToNext()
        {
            while (treeNodeIterator.MoveNext())
            {
                TreeNodeItem item = treeNodeIterator.Current;
                SElement ancestor = item.Element;
                switch (item.Location)
                {
                    case TreeNodeLocation.None:
                        Debug.Assert(false, "The tree node iterator is moved to the 'None' item.");
                        throw new InvalidOperationException();
                    case TreeNodeLocation.Ancestor:
                        if (ancestor.Parent != null && ancestor.Parent.HasDeclaration && CanAllocate(block, ancestor.Parent))
                        {
                            if (allocateBefore)
                                block.AppendBefore(ancestor);
                            else
                                block.AppendAfter(ancestor);
                            current = ancestor.Parent;
                            return true;
                        }
                        break;
                    case TreeNodeLocation.Previous:
                        if (CanAllocate(block, ancestor))
                        {
                            block.InsertAtEnd(ancestor);
                            current = ancestor;
                            return true;
                        }
                        break;
                    case TreeNodeLocation.Next:
                        if (CanAllocate(block, ancestor))
                        {
                            block.InsertAtStart(ancestor);
                            current = ancestor;
                            return true;
                        }
                        break;
                    default:
                        throw new InvalidOperationException("Unknown the type of the tree node item.");
                }
            }
            current = null;
            return false;
        }

        public SElement Current
        {
            get { return current; }
        }

        public void Reset(InconsistentBlock inconsistentBlock)
        {
            Debug.Assert(inconsistentBlock != null);
            Guard.ArgumentNotNull(inconsistentBlock, "inconsistentBlock");
            Debug.Assert(inconsistentBlock.Count > 0);

            current = null;
            allocateBefore = false;
            block = inconsistentBlock;
            if (block.Count != block.Ancestor.Nodes.Count)
            {
                float midline = (block.Ancestor.Nodes.Count - 1) / 2f;
                if (block.StartIndex < midline)
                {
                    if (block.EndIndex > midline)
                    {
                        if ((midline - block.StartIndex) > (block.EndIndex - midline))
                            allocateBefore = true;
                    }
                    else
                        allocateBefore = true;
                }
            }
            TreeNodeItem firstItem = default(TreeNodeItem);
            TreeNodeItem secondItem = default(TreeNodeItem);
            if (allocateBefore)
                firstItem = new TreeNodeItem
                {
                    Element = block.FirstElement.PreviousNonInclusionNode(),
                    Location = TreeNodeLocation.Previous
                };
            else
                secondItem = new TreeNodeItem
                {
                    Element = block.FirstElement.PreviousNonInclusionNode(),
                    Location = TreeNodeLocation.Previous
                };
            if (allocateBefore)
                secondItem = new TreeNodeItem
                {
                    Element = block.LastElement.NextNonInclusionNode(),
                    Location = TreeNodeLocation.Next
                };
            else
                firstItem = new TreeNodeItem
                {
                    Element = block.LastElement.NextNonInclusionNode(),
                    Location = TreeNodeLocation.Next
                };
            treeNodeIterator.Reset(block.Ancestor, ref firstItem, ref secondItem);
        }

        protected bool CanAllocate(InconsistentBlock block, SElement ancestor)
        {
            Debug.Assert(block != null);
            Debug.Assert(ancestor != null);
            if (ancestor.HasDeclaration)
                for (int i = 0; i < block.Count; i++)
                {
                    if (CanAllocate(block[i], ancestor))
                        return true;
                }
            return false;
        }

        private bool CanAllocate(SElement element, SElement ancestor)
        {
            return element.DefinedInScheme && rules[ancestor].CanContain(element, ancestor);
        }
    }
}
